package AlienArena::Server;
use lib '../..';
use Carp qw(croak carp);
use Time::HiRes qw(usleep);
use AlienArena::Server::CVar;
use AlienArena::Server::CVar::DMFlags;
use AlienArena::Server::Player;
use strict;
use warnings;

our %CVAR_FLAGS = %AlienArena::Server::CVar::CVAR_FLAGS;

# init_console : returns true if connection succesful
sub init_console {
    my $self = shift;
    my $con = $self->console;
    unless ($con && $con->isa('AlienArena::RCon::Console')) {
	croak 'console not set for this server object';
    }
    $con->host($self->host);
    $con->port($self->port);
    $con->pass($self->pass);
    return $con->connected;
}

# update_runstate
sub update_runstate {
    my $self = shift;
    return unless $self->_check_console;
    $self->console->send_command('serverinfo');
    unless ($self->console->ok) {
	carp 'server connection problems';
	return;
    }
    $self->_run_state->_is_up(1);
    my $response = $self->console->response->parsed_response;
    foreach ( split(/\n/,$response) ) {
	next if m/Server info settings:/;
	my ($name, $value) = /(\S+)\s+(.*)/;
	my $cvar = $self->get_cvar($name);
	if ($cvar) { # cvar exists in server object
	    $cvar->value($value);
	}
	else { # new cvar obj required
	    if ($name eq 'dmflags') {
		$cvar = AlienArena::Server::CVar::DMFlags->new($value);
	    }
	    else {
		$cvar = AlienArena::Server::CVar->new($name,$value,qw(SERVERINFO));
	    }
	    $self->_add_cvars($cvar);
	}
    }

    # update RunState object
    my $runstate = $self->_run_state;
    $runstate->_map( $self->get_cvar('mapname')->value );
    1;
}

sub update_cvars {
    my $self = shift;
    return unless ($self->_check_console); 
    # poll several times to get all cvars
    # evidently only partial results can make it back to the console
    my $response ="";
    my ($all_received,$cvar_num, %cvarlist);
    my $MAX_TRIES = 25;
    while (!$all_received && ($MAX_TRIES>0)) {
#	print $MAX_TRIES;
	$self->console->send_command('cvarlist');
	unless ($self->console->ok) {
	    carp 'server connection problems';
	    return;
	}
	$response .= $self->console->response->as_text;
	foreach ( split(/\n/,$response) ) {
	    next if /^print/;
	    if ( ($cvar_num) = /([0-9]+) cvars/ ) {
		next;
	    }
	    else {
		no warnings;
		my ($cvar_flags,$name,$value) = /^([ *-USL]+)*(\S+)\s+(.*)$/;
		$value =~ s/"//g;
		next unless $name;
		$cvarlist{$name} = [$cvar_flags,$value];
	    }
	}
	$all_received = 1 if ($cvar_num && (scalar keys %cvarlist >= $cvar_num));
	$MAX_TRIES--;
	usleep 300;
    }
    $self->_run_state->_is_up(1);
    foreach my $name ( keys %cvarlist ) {
	my ($cvar_flags,$value) = @{$cvarlist{$name}};
	my $cvar = $self->get_cvar($name);
	if ($cvar) { # cvar exists in server object
	    $cvar->value($value);
	}
	else { # new cvar obj required
	    if ($name eq 'dmflags') {
		$cvar = AlienArena::Server::CVar::DMFlags->new($value);
	    }
	    else {
		my @properties;
		for my $flag (split '',$cvar_flags) {
		    next if $flag eq ' ';
		    push @properties, $CVAR_FLAGS{$flag};
		}
		$cvar = AlienArena::Server::CVar->new($name,$value,@properties);
	    }
	    $self->_add_cvars($cvar);
	}
    }
    # set objects
    $self->_run_state->_map( $self->get_cvar('mapname')->value );
    1;
}

sub update_players {
    my $self = shift;
    return unless $self->_check_console;
    my $resp = $self->console->send_command('status');
    my $response = $resp->parsed_response;
    my @lines = split(/\n/,$response);
    my @hdrs;
    my @players;
    foreach ( @lines ) {
	/^map/ && do {
	    next;
	};
	/^---/ && do {
	    next;
	};
	/^num / && do {
	    @hdrs = split /\s+/;
	    next;
	};
	do { # player info
	    my %pdata;
	    s/^\s+//;
	    @pdata{@hdrs} = split /\s+/;
	    my ($ip) = $pdata{address} =~ /^([^:]+)/; # ip only not port
	    my $player = AlienArena::Server::Player->new($pdata{name}, $ip);
	    $player->_is_playing(1);
	    $player->_score($pdata{score});
	    push @players, $player;
	};
    }
    $self->_update_player_list(@players) if @players;
}

# _check_console
# check console object and connection
# return true if connected, false if not

sub _check_console {
    my $self = shift;
    my $con = $self->console;
    unless ( $con && $con->isa('AlienArena::RCon::Console') ) {
	croak 'console not set for this server object';
    }
    unless ( $con->connected ) {
	$self->_run_state->_is_up = 0;
	carp 'console not connected to server';
	return;
    }
    return 1;
}

=head1 NAME

AlienArena::Server::ORM - ORM mixin for AlienArena::Server

=head1 SYNOPSIS

    $server->init_console;
    $server->update_runstate;
    print "Dude! Server's up!\n" if ($server->is_up);
    $server->update_cvars;
    $server->update_players;

=head1 DESCRIPTION

Defines mappings between responses to rcon console commands and the
AA::Server constituent objects. These methods are called to keep
the AA::Server object up to date with respect to the running server.

=head1 METHODS

=head2 init_console

=head2 update_runstate

=head2 update_cvars

=head2 update_players

=head2 _check_console

=head1 AUTHOR

Mark A. Jensen, C<< <maj -at- fortinbras -dot- us> >>

=head1 LICENSE AND COPYRIGHT

Copyright 2012 Mark A. Jensen.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.

=cut

1;
